// .NET Framework Namespaces
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

//NUnit Namespaces
using NUnit.Framework;

// Project Namespaces
using RNChessRulesEngine;
using RNChessRulesEngine.RNChessRulesExceptions;
using RNChessBoardCommonTypes;

namespace RNChessBoard.RNChessRulesEngine.TestFixtures
{
    /// <summary>
    /// This class is the base definition for test fixtures that
    /// exercise the RNChessRulesEngine assembly and types.
    /// </summary>
    [TestFixture]
    public class BaseChessRulesEngineTestFixture
    {
        protected ChessRulesEngine _chessRulesEngine;


        [TestFixtureSetUp]
        protected virtual void TestFixtureSetUp()
        {

        }

        [TestFixtureTearDown]
        protected virtual void TestFixtureTearDown()
        {

        }

        /// <summary>
        /// This method is executed on a per-test basis to set up
        /// unit test objects, member variables, etc.
        /// </summary>
        [SetUp]
        protected virtual void TestSetUp()
        {
            _chessRulesEngine = new ChessRulesEngine();
        }

        /// <summary>
        /// This method is executed on a per-test basis to tear-down
        /// unit test objects, member variables, etc.
        /// </summary>
        [TearDown]
        protected virtual void TestTearDown()
        {

        }

        // ================== TEST HELPER METHODS ==================

        /// <summary>
        /// Test helper method that runs a "battery" of test game moves from
        /// a jagged array of play test move arrays.
        /// </summary>
        /// <param name="testGameBattery">
        /// Jagged array of strings for the battery.
        /// </param>
        protected void ReplayTestGameBattery(string[][] testGameBattery)
        {
            int gameBatteryCount = 1;
            foreach (string[] testGame in testGameBattery)
            {
                //Console.WriteLine("Replaying Test Game #" + gameBatteryCount.ToString());
                ReplayTestGame(testGame);
                gameBatteryCount++;

                // reset the chess engine
                _chessRulesEngine = new ChessRulesEngine();
            }
        }

        /// <summary>
        /// Test helper method that runs a test game from the <see cref="TestGames"/> class,
        /// applying assertions according to move results and types.
        /// </summary>
        /// <param name="testGame"></param>
        protected void ReplayTestGame(string[] testGame)
        {
            PlayerMoveResult pmr;
            int moveIndex = 1;

            foreach (string move in testGame)
            {
                // split into constituent parts
                // 0: White Move From/To
                // 1: Expected Result
                // 2: Expected Move Type
                // 3: Black Move From/To
                // 4: Expected Result
                // 5: Expected Move Type
                string[] moveParts = move.Split('/');
                if (moveParts.Length != 6)
                    throw new ApplicationException("Test game move string has too many elements:\n" +
                        move);

                // White to move...
                if (moveParts[0].Trim().ToUpper() == "EOG")
                    break;
                pmr = ExecuteTestGameMove(moveIndex, moveParts[0], moveParts[1], moveParts[2]);

                // Break out if we have mate - no further moves possible
                if (pmr.PlayerMoveStatus == MoveResult.MATE)
                    break;

                // Test black move and result
                if (moveParts[3].Trim().ToUpper() == "EOG")
                    break;
                pmr = ExecuteTestGameMove(moveIndex, moveParts[3], moveParts[4], moveParts[5]);

                // Break out if we have mate - no further moves possible
                if (pmr.PlayerMoveStatus == MoveResult.MATE)
                    break;

                moveIndex++;
            }
        }

        /// <summary>
        /// This method runs NUnit assertions for a move.
        /// </summary>
        /// <param name="moveIndex">Int32 specifying the current full move number; this is used for feedback if the assert fails.</param>
        /// <param name="SANMove">The move to be tested in algebraic notation (eg. C2-C4)</param>
        /// <param name="moveResult"><see cref="PlayerMoveResult"/> object returned from the chess engine.</param>
        /// <param name="moveType"><see cref="PlayerMoveType"/> object returned from the chess engine.</param>
        /// <returns></returns>
        protected PlayerMoveResult ExecuteTestGameMove(
            int moveIndex,
            string SANMove,
            string moveResult,
            string moveType)
        {
            PlayerMoveResult pmr;

            // Test white move and result
            GroupCollection playerMove = Regex.Match
                (SANMove, @"([A-Ha-h][0-9])\-([A-Ha-h][0-9])").Groups;
            pmr = _chessRulesEngine.MakeMove(playerMove[1].Value, playerMove[2].Value);

            //string playerMoveResult = GetMoveResultStringAsSAN(pmr.PlayerMoveStatus);
            //string playerMoveType = GetMoveTypeStringAsSAN(pmr.PlayerMoveType);

            MoveResult expectedMoveResult = GetMoveResultFromSANCode(moveResult);
            MoveType expectedMoveType = GetMoveTypeFromSANCode(moveType);

            Assert.AreEqual(
                expectedMoveResult,
                pmr.PlayerMoveStatus,
                "Invalid " + pmr.PlayerSide.ToString() + " move result returned at move # " + moveIndex.ToString() +
                " SAN: " + SANMove);

            Assert.AreEqual(
                expectedMoveType,
                pmr.PlayerMoveType,
                "Invalid " + pmr.PlayerSide.ToString() + " move type returned at move # " + moveIndex.ToString() +
                " SAN: " + SANMove);

            return pmr;
        }

        /// <summary>
        /// Returns string containing the SAN representation for a given <see cref="MoveResult"/>
        /// enum type.
        /// </summary>
        /// <param name="moveResult"></param>
        /// <returns></returns>
        /// <remarks>
        /// This method returns flags that correspond to the result codes in the
        /// test games arrays.
        /// </remarks>
        protected string GetMoveResultStringAsSAN(MoveResult moveResult)
        {
            if (moveResult == MoveResult.CHECK)
                return "+";
            else if (moveResult == MoveResult.ILLEGAL)
                return "!";
            else if (moveResult == MoveResult.LEGAL)
                return "";
            else if (moveResult == MoveResult.MATE)
                return "++";
            else
                return "";

        }

        /// <summary>
        /// Returns the <see cref="MoveResult"/> enum equivalent for a SAN-style
        /// move result code.
        /// </summary>
        /// <param name="SANCode"></param>
        /// <returns></returns>
        protected MoveResult GetMoveResultFromSANCode(string SANCode)
        {
            StringBuilder sb = new StringBuilder(SANCode.Trim().ToLower());
            if (Regex.Match(sb.ToString(), "^!$").Success)
                return MoveResult.ILLEGAL;
            if (Regex.Match(sb.ToString(), "^$").Success)
                return MoveResult.LEGAL;
            if (Regex.Match(sb.ToString(), @"^\+$").Success)
                return MoveResult.CHECK;
            if (Regex.Match(sb.ToString(), @"^\+\+$").Success)
                return MoveResult.MATE;

            throw new ArgumentException("Unexpected SANCode: [" +
                SANCode + "]");
        }

        /// <summary>
        /// Returns string containing the SAN representation for a given <see cref="MoveType"/>
        /// enum type.
        /// </summary>
        /// <param name="moveType"></param>
        /// <returns></returns>
        /// <remarks>
        /// This method returns flags that correspond to the move type codes in the
        /// test games arrays.
        /// </remarks>
        protected string GetMoveTypeStringAsSAN(MoveType moveType)
        {
            if (moveType == MoveType.CAPTURE)
                return "x";
            else if (moveType == MoveType.KINGSIDE_CASTLE)
                return "O-O";
            else if (moveType == MoveType.QUEENSIDE_CASTLE)
                return "O-O-O";
            else if (moveType == MoveType.ENPASSANT)
                return "xep";
            else if (moveType == MoveType.ILLEGAL)
                return "!";
            else if (moveType == MoveType.PROMOTION)
                return "^";
            else if (moveType == MoveType.NONCAPTURE)
                return "nc";
            else
                return "";
        }

        /// <summary>
        /// Returns the <see cref="MoveType"/> enum equivalent for a SAN-style
        /// move code.
        /// </summary>
        /// <param name="SANCode">
        /// One of: 
        /// "x" for CAPTURE.
        /// "O-O" for kingside CASTLE
        /// "O-O-O" for queenside CASTLE
        /// "xep" for EN PASSANT CAPTURE
        /// "!" for ILLEGAL
        /// "nc" for NON CAPTURING
        /// </param>
        /// <returns></returns>
        protected MoveType GetMoveTypeFromSANCode(string SANCode)
        {
            StringBuilder sb = new StringBuilder(SANCode.Trim().ToLower());

            if (Regex.Match(sb.ToString(), "^x$").Success)
                return MoveType.CAPTURE;
            if (Regex.Match(sb.ToString(), "^o-o$").Success)
                return MoveType.KINGSIDE_CASTLE;
            if (Regex.Match(sb.ToString(), "^o-o-o$").Success)
                return MoveType.QUEENSIDE_CASTLE;
            if (Regex.Match(sb.ToString(), "^xep$").Success)
                return MoveType.ENPASSANT;
            if (Regex.Match(sb.ToString(), "^!$").Success)
                return MoveType.ILLEGAL;
            if (Regex.Match(sb.ToString(), @"^\^$").Success)
                return MoveType.PROMOTION;
            if (Regex.Match(sb.ToString(), "^nc$").Success)
                return MoveType.NONCAPTURE;

            throw new ArgumentException("Unexpected SANCode: [" +
                SANCode + "]");
        }

    }
}
